Node.js のログを Fluentd を使って MongoDB に集約する
最近 Fluentd をプロジェクトで導入しようと考えています。
Fluentd は、あらゆるデータのログを json を使ってシンプルに転送、集約させることができるツールです。
クラウド環境やビッグデータを扱う上で、今後需要が高くなりそうです。
ソーシャルゲーム業界などではかなり実績があるみたいですね。
今回お試しとして、Node.js のエラーログを Fluentd を使って MongoDB に保存するサンプルを作ってみます。
こんな感じ。とてもシンプルです。。
Input Output +------------------------------------------------+ | | | Node.js --------> Fluentd --------> MongoDB | | | +------------------------------------------------+
インストール
パッケージ形式の td-agent と gem 形式の Fluentd があるみたいですが、
今回はお試しなので、gem を使ってインストールしてみました。(ちなみに Mac OS 10.8 です)
大規模環境では td-agent を使う方がいいと思います。
Ruby の1.9.2以上が必要です。
$ gem install fluentd --no-ri --no-rdoc
起動確認
マニュアルに書いてある通りやってみます。
セットアップ
$ fluentd --setup ./fluent Installed ./fluent/fluent.conf.
起動
$ fluentd -c ./fluent/fluent.conf -vv &
すんなり起動しました。
MongoDB Output Plugin をインストール
Fluentd はプラグインが豊富です。
今回は、MongoDB の プラグインを fluent-gem というのを使ってインストールします。(td-agentの場合は入っている)
$ fluent-gem install fluent-plugin-mongo
ログの設定
MongoDB のプラグインをインストールしたら、fluent.conf に追記します。
# Single MongoDB <match mongo.**> type mongo host localhost port 27017 database node collection error # for capped collection capped capped_size 1024m # flush flush_interval 10s </match>
match mongo.** で受け取るタグを正規表現で指定できます。
この場合、mongo、mongo.x、mongo.x.x... の条件にマッチします。
type 〜 collection は、MongoDB の接続先と保存先のコレクションです。
capped collection というのは、MongoDB のサイズ固定のコレクションです。
これはロギング処理に向いていますので、Fluentd ではこれが推奨されています。
10秒間隔で MongoDB にフラッシュします。
Logger のインストール(Node.js)
続いて、Node.js 側です。
ちなみに express を使ってます。
以下のようにスケルトンを作成したら、package.json を編集します。
express fluentd-sample
「package.json」
{ "name": "application-name", "version": "0.0.1", "private": true, "scripts": { "start": "node app" }, "dependencies": { "express": "3.0.3", "fluent-logger": "latest", "jade": "*" } }
fluentd の Logger である fluent-logger を使う指定をします。
続けて、npm start。
$ npm start fluent-logger@0.2.1 node_modules/fluent-logger └── msgpack@0.1.7
0.2.1がインストールされました。
例外処理のコード(Node.js)
例外処理を記述します。
ちなみに CoffeeScript です。
「app.coffee」
'use strict' ### Module dependencies. ### express = require 'express' routes = require './routes' path = require 'path' logger = require 'fluent-logger' logger.configure 'mongo', {host: 'localhost', port: 24224} app.configure -> app.set 'port', process.env.PORT or 3000 app.set 'views', __dirname + '/views' app.set 'view engine', 'jade' app.use express.favicon() app.use express.logger 'dev' app.use express.bodyParser() app.use express.methodOverride() app.use app.router app.use express.static path.join(__dirname, 'public') app.use (err, req, res, next) -> logger.emit 'test', {error: err.message} res.render 'error', {status: 500, title: '500 Internal Server Error', err: err} app.get '/', routes.index app.listen process.env.PORT || 3000, -> console.log 'Express server listening on port ' + app.get 'port'
エラーをハンドリングしたら、Fluentd にログを飛ばし、その後エラー画面に遷移させています。
その際のタグとラベルの組み合わせが mongo.test と指定しているので、先ほどの fluent.conf の構文にマッチするというわけです。
routes/index では、テスト的にわざとエラーを発生させるようにします。
「routes/index.coffee」
'use strict' ### Module dependencies. ### exports.index = (req, res, next) -> next new Error('fluentd test')
エラーを発生させる
Fluentd を起動しておいて、MongoDB も起動します。
$ sudo mongod
そして Node.js を起動し、curl でアクセスします。
$ node app.js $ curl localhost:3000/ <!DOCTYPE html><html><head><title>500 Internal Server Error</title><link rel="stylesheet" href="/stylesheets/style.css"></head><body><h1>500 Internal Server Error</h1><h1>Error: Fluentd test</h1></body></html>
ログの確認
ちょっと待って MongoDB を確認してみます。
$ mongo $ use node $ db.error.find() { "_id" : ObjectId("5105183acaf52f08dd00000e"), "error" : "Fluentd test", "time" : ISODate("2013-01-27T12:06:15.513Z") }
ちゃんと出力されてますね。
まとめ
Fluentd と MongoDB はデータを json 形式で扱うため、高いスループットでの挿入が実現できます。
(もちろん Node.js も JavaScript なので json は親和性が高いです)
MongoDB の capped コレクションに保存すると、古いデータが消えていってしまうので、同時にS3にも保存した方がいいかもしれません。
同時にS3にも保存する方法は、以下のブログに書いてあります。
Amazon LinuxにFluentdをインストールしてS3とMongoDB連携する
あとは定期的に Amazon Glacier にアーカイブするなどもいいですね。